// file:src/system/interrupt.c
// autor:jiangxinpeng
// time:2021.4.25
// copyright:(C) 2020-2050 by jiangxinpeng,All right are reserved.

#include<os/schedule.h>
#include <os/hardirq.h>
#include <os/memcache.h>
#include <os/softirq.h>
#include <arch/interrupt.h>
#include <arch/atomic.h>
#include <lib/type.h>
#include <lib/list.h>

#pragma GCC optimize("O0")

irq_descript_t irq_descript_table[HARDIRQ_MAX];

// init irq descript table
void IrqDescriptInit()
{
    int i;
    irq_descript_t *irq;

    for (int i = 0; i < HARDIRQ_MAX; i++)
    {
        irq = irq_descript_table + i; // irq point descript

        list_init(&irq->action_list_head);
        irq->controller = NULL;
        AtomicSet(&irq->device_count, 0);
        irq->irqname = NULL;
        irq->flags = 0;
    }
    KPrint("[irq] init irq descript ok!\n");
}

inline irq_descript_t *IrqDescriptGet(uint32_t irq)
{
    if (irq >= 0 && irq < IRQ_NUM_MAX)
    {
        return irq_descript_table + irq;
    }
}

int IrqRegister(irqno_t irq, irq_handler_t handler, uint64_t flags, char *irqname, char *actname, void *data)
{
    irq_descript_t *irq_descript = IrqDescriptGet(irq);
    irq_action_t *action;

    // init irq descript
    if (!irq_descript)
        return -1;
    irq_descript->controller = &interrupt_controller;
    irq_descript->flags = flags;
    irq_descript->irqname = irqname;

    // alloc irq action
    action = (irq_action_t *)KMemAlloc(sizeof(irq_action_t));
    if (!action)
    {
        KPrint("[hardirq] alloc mem for irq action err\n");
        return -1;
    }
    action->data = data;
    action->handler = handler;
    action->name = actname;
    action->flags = flags;
    list_init(&action->list);

    // whether is share irq
    if (flags & IRQ_SHARE)
    {
        list_add_tail(&action->list, &irq_descript->action_list_head);
    }
    else
    {
        list_add_head(&action->list, &irq_descript->action_list_head);
    }
    AtomicInc(&irq_descript->device_count);
    // controller install irq and handler
    irq_descript->controller->install(irq, handler);
    // enable interrupt on hardware
    irq_descript->controller->enable(irq);
    return 0;
}

int IrqUnregister(irqno_t irq, void *data)
{
    irq_descript_t *irq_descript = IrqDescriptGet(irq);
    irq_action_t *action;

    InterruptDisable();
    // share irq
    if (irq_descript->flags & IRQ_SHARE)
    {
        list_traversal_all_owner_to_next(action, &irq_descript->action_list_head, list)
        {
            if (action->data == data)
            {
                list_del_init(&action->list);
                KMemFree(action);
            }
        }
    }
    else
    {
        // no share irq
        action = list_first_owner(&irq_descript->action_list_head, irq_action_t, list);
        if (!list_is_head(&action->list))
        {
            list_del_init(&action->list);
            KMemFree(action);
        }
    }
    AtomicDec(&irq_descript->device_count);
    if (!AtomicGet((&irq_descript->device_count)))
    {
        irq_descript->controller->disable(irq);   // disalbe interrupt on hardware
        irq_descript->controller->uninstall(irq); // uninstall interrupt on hardware
        irq_descript->flags = 0;
        irq_descript->irqname = NULL;
    }
    InterruptEnable();
    return 0;
}

static inline int DoHandleAction(irqno_t irq, irq_action_t *action)
{
    uint32_t ret;
    uint32_t eflags = 0;

    if (action->flags & IRQ_DISABLE)
    {
        InterruptDisable();
    }

    // handler action
    ret = action->handler(irq, action->data);

    if (action->flags & IRQ_DISABLE)
    {
        InterruptEnable();
    }
    return ret;
}

int IrqHandle(irqno_t irq, trap_frame_t *frame)
{
    irq_descript_t *irq_descript = IrqDescriptGet(irq);
    irq_action_t *action = NULL;

    if (!irq_descript)
        return -1;

   /*  if (list_empty(&irq_descript->action_list_head))
        return -1; */

    // deal with all irq action
    list_traversal_all_owner_to_next(action, &irq_descript->action_list_head, list)
    {
        if (DoHandleAction(irq, action) < 0)
            break;
    }

    uint32_t irqsave=InterruptDisableStore();
    // send eof cmd to end interrupt
    if (irq_descript->controller->ack)
    {     
        irq_descript->controller->ack(irq); 
        
        if(sched_flags)
        {
            sched_flags=0;
            Schedule();
        }
    }
    InterruptEnableRestore(irqsave);
    return 0;
}
